/* AccentedAtom.java
 * =========================================================================
 * This file is originally part of the JMathTeX Library - http://jmathtex.sourceforge.net
 *
 * Copyright (C) 2004-2007 Universiteit Gent
 * Copyright (C) 2009 DENIZET Calixte
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * A copy of the GNU General Public License can be found in the file
 * LICENSE.txt provided with the source distribution of this program (see
 * the META-INF directory in the source jar). This license can also be
 * found on the GNU website at http://www.gnu.org/licenses/gpl.html.
 *
 * If you did not receive a copy of the GNU General Public License along
 * with this program, contact the lead developer, or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 *
 * Linking this library statically or dynamically with other modules 
 * is making a combined work based on this library. Thus, the terms 
 * and conditions of the GNU General Public License cover the whole 
 * combination.
 * 
 * As a special exception, the copyright holders of this library give you 
 * permission to link this library with independent modules to produce 
 * an executable, regardless of the license terms of these independent 
 * modules, and to copy and distribute the resulting executable under terms 
 * of your choice, provided that you also meet, for each linked independent 
 * module, the terms and conditions of the license of that module. 
 * An independent module is a module which is not derived from or based 
 * on this library. If you modify this library, you may extend this exception 
 * to your version of the library, but you are not obliged to do so. 
 * If you do not wish to do so, delete this exception statement from your 
 * version.
 * 
 */

package org.scilab.forge.jlatexmath;

/**
 * An atom representing another atom with an accent symbol above it.
 */
public class AccentedAtom extends Atom {

    // accent symbol
    private final SymbolAtom accent;
    private boolean acc = false;
    private boolean changeSize = true;

    // base atom
    protected Atom base = null;
    protected Atom underbase = null;

    public AccentedAtom(Atom base, Atom accent) throws InvalidSymbolTypeException {
        this.base = base;
        if (base instanceof AccentedAtom)
            underbase = ((AccentedAtom) base).underbase;
        else
            underbase = base;

        if (!(accent instanceof SymbolAtom))
            throw new InvalidSymbolTypeException("Invalid accent");

        this.accent = (SymbolAtom) accent;
        this.acc = true;
    }

    public AccentedAtom(Atom base, Atom accent, boolean changeSize) throws InvalidSymbolTypeException {
        this(base, accent);
        this.changeSize = changeSize;
    }

    /**
     * Creates an AccentedAtom from a base atom and an accent symbol defined by its name
     *
     * @param base       base atom
     * @param accentName name of the accent symbol to be put over the base atom
     * @throws InvalidSymbolTypeException if the symbol is not defined as an accent ('acc')
     * @throws SymbolNotFoundException    if there's no symbol defined with the given name
     */
    public AccentedAtom(Atom base, String accentName)
            throws InvalidSymbolTypeException, SymbolNotFoundException {
        accent = SymbolAtom.get(accentName);
        if (accent.type == TeXConstants.TYPE_ACCENT) {
            this.base = base;
            if (base instanceof AccentedAtom)
                underbase = ((AccentedAtom) base).underbase;
            else
                underbase = base;
        } else
            throw new InvalidSymbolTypeException("The symbol with the name '"
                    + accentName + "' is not defined as an accent ("
                    + TeXSymbolParser.TYPE_ATTR + "='acc') in '"
                    + TeXSymbolParser.RESOURCE_NAME + "'!");
    }

    /**
     * Creates an AccentedAtom from a base atom and an accent symbol defined as a TeXFormula.
     * This is used for parsing MathML.
     *
     * @param base base atom
     * @param acc  TeXFormula representing an accent (SymbolAtom)
     * @throws InvalidTeXFormulaException if the given TeXFormula does not represent a
     *                                    single SymbolAtom (type "TeXConstants.TYPE_ACCENT")
     * @throws InvalidSymbolTypeException if the symbol is not defined as an accent ('acc')
     */
    public AccentedAtom(Atom base, TeXFormula acc)
            throws InvalidTeXFormulaException, InvalidSymbolTypeException {
        if (acc == null)
            throw new InvalidTeXFormulaException(
                    "The accent TeXFormula can't be null!");
        else {
            Atom root = acc.root;
            if (root instanceof SymbolAtom) {
                accent = (SymbolAtom) root;
                if (accent.type == TeXConstants.TYPE_ACCENT)
                    this.base = base;
                else
                    throw new InvalidSymbolTypeException(
                            "The accent TeXFormula represents a single symbol with the name '"
                                    + accent.getName()
                                    + "', but this symbol is not defined as an accent ("
                                    + TeXSymbolParser.TYPE_ATTR + "='acc') in '"
                                    + TeXSymbolParser.RESOURCE_NAME + "'!");
            } else
                throw new InvalidTeXFormulaException(
                        "The accent TeXFormula does not represent a single symbol!");
        }
    }

    public Box createBox(TeXEnvironment env) {
        TeXFont tf = env.getTeXFont();
        int style = env.getStyle();

        // set base in cramped style
        Box b = (base == null ? new StrutBox(0, 0, 0, 0) : base.createBox(env.crampStyle()));

        float u = b.getWidth();
        float s = 0;
        if (underbase instanceof CharSymbol)
            s = tf.getSkew(((CharSymbol) underbase).getCharFont(tf), style);

        // retrieve best Char from the accent symbol
        Char ch = tf.getChar(accent.getName(), style);
        while (tf.hasNextLarger(ch)) {
            Char larger = tf.getNextLarger(ch, style);
            if (larger.getWidth() <= u)
                ch = larger;
            else
                break;
        }

        // calculate delta
        float ec = -SpaceAtom.getFactor(TeXConstants.UNIT_MU, env);
        float delta = acc ? ec : Math.min(b.getHeight(), tf.getXHeight(style, ch.getFontCode()));

        // create vertical box
        VerticalBox vBox = new VerticalBox();

        // accent
        Box y;
        float italic = ch.getItalic();
        Box cb = new CharBox(ch);
        if (acc)
            cb = accent.createBox(changeSize ? env.subStyle() : env);

        if (Math.abs(italic) > TeXFormula.PREC) {
            y = new HorizontalBox(new StrutBox(-italic, 0, 0, 0));
            y.add(cb);
        } else
            y = cb;

        // if diff > 0, center accent, otherwise center base
        float diff = (u - y.getWidth()) / 2;
        y.setShift(s + (diff > 0 ? diff : 0));
        if (diff < 0)
            b = new HorizontalBox(b, y.getWidth(), TeXConstants.ALIGN_CENTER);
        vBox.add(y);

        // kern
        vBox.add(new StrutBox(0, changeSize ? -delta : -b.getHeight(), 0, 0));
        // base
        vBox.add(b);

        // set height and depth vertical box
        float total = vBox.getHeight() + vBox.getDepth(), d = b.getDepth();
        vBox.setDepth(d);
        vBox.setHeight(total - d);

        if (diff < 0) {
            HorizontalBox hb = new HorizontalBox(new StrutBox(diff, 0, 0, 0));
            hb.add(vBox);
            hb.setWidth(u);
            return hb;
        }

        return vBox;
    }
}
